#pragma once
#include "stdafx.h"
#include "dgdef.h"
#include "ASN.h"
#include "sha2.h"
#include <fstream>

/*
Functions takes path to certificate as input, length and outputs its content in BYTE array, and puts length of certificate read in length
*/

namespace efsod {

static BYTE* readCertificate(const char* path, int *len)
{
char * mem;
int size;

	ifstream file (path, ios::in|ios::binary|ios::ate);
		if (file.is_open()) {
			size = file.tellg();
		    	mem = new char [size];
    			file.seekg (0, ios::beg);
	    		file.read (mem, size);
		    	file.close();
			*len = size;
			return (BYTE*) mem;
		} else return NULL;
}

/*
Arguments: data group, hash algorithm to compute hash of data of data group
Returns: DO of the form
	Sequence of
		Integer DGNumber
		OctetString HashValue of DGData
*/
static DO* createDataGroupHash(DG *d, int hashAlgo) {
	BYTE out = DGNumByte[d->DGNum];//DGNum specifies the index of the DG i.e. 1, 2, 11, 12, 13
	ASNInteger *DGNum = new ASNInteger(&out, 1);
	
	OctetString *DGHashValue;
	BYTE *buf;
	
	switch (hashAlgo) {
	case SHA1: 
		buf = new BYTE[SHA1_DIGEST_LENGTH];
		SHA1_Data(d->DGData, d->DGLen, buf);
		DGHashValue = new OctetString(SHA1_LENGTH, buf); 
		break;
	case SHA256:
		buf  = new BYTE[SHA256_DIGEST_LENGTH];
		SHA256_Data(d->DGData, d->DGLen, buf);
		DGHashValue = new OctetString(SHA256_LENGTH, buf); 
		break;
	case SHA384: 
		buf  = new BYTE[SHA384_DIGEST_LENGTH];
		SHA384_Data(d->DGData, d->DGLen, buf);
		DGHashValue = new OctetString(SHA384_LENGTH, buf); 
		break;
	case SHA512:
		buf  = new BYTE[SHA512_DIGEST_LENGTH];
		SHA512_Data(d->DGData, d->DGLen, buf);
		DGHashValue = new OctetString(SHA512_LENGTH, buf); 
		break;
	}

	list<DO> l;
	l.push_back(*DGNum);
	l.push_back(*DGHashValue);

	Sequence *seq = new Sequence(l);
	return seq;
}

/*
Arguments: ObjectIdentifier of the HashAlgorithm
Returns: DO of the form
	Sequence of
		ObjectIdentifer of the hash algorithm
		Parameters
*/
static DO* createHashAlgorithm(ObjectIdentifier *oid) {
	DO *param = new Null();
	list<DO> l;
	l.push_back(*oid);
	l.push_back(*param);

	Sequence *hashAlgo = new Sequence(l);
	return hashAlgo;
}

/*
Function used to make Digest Algorithms
Returns DO of the for of
	Set of
		all Hash Algorithms allowed
*/
static DO* createDigestAlgorithms() {
	list<DO> l;
	ObjectIdentifier *t;
	for(int i=0; i<4; i++) {
		t = new ObjectIdentifier(algolist[i].oid, algolist[i].oidLength);	
		l.push_back(*createHashAlgorithm(t));
	}
	
	Set *s = new Set(l);
	return s;
}
/*
Arguments: DO of ldsSecurityObject
Returns: DO of the form
	Context Specific
		Sequence of
			eContentType: Object Identifier for LDSSecurityObject
			eContent: Octet String of LDSSecurityObject	
*/
static DO* createEncapContentInfo(DO *ldsSecurityObject) {
	ObjectIdentifier *contentType = new ObjectIdentifier(ldsSOoid, ldsSOoidLen);
	OctetString *eContent = new OctetString(ldsSecurityObject);
	
	list<DO> n;
	n.push_back(*eContent);
	ContextSpecific *cs = new ContextSpecific(0, n);
	
	list<DO> m;
	m.push_back(*contentType);
	m.push_back(*cs);
	Sequence *seq = new Sequence(m);

	return seq;
}

/*
Function makes the content contentTypeAttribute which is used to make SignedAttributes
RFC3369: The content type attribute value must match the encapContentInfo eContentType value in the signed data
Returns: Do of the form
	Sequence of
		Object Identifier of content type attribute
		Object Identifier(?)
*/
static DO* createContentTypeAttr() {
	ObjectIdentifier *ct = new ObjectIdentifier(contentTypeoid, contentTypeoidLen);
	ObjectIdentifier *any = new ObjectIdentifier(ldsSOoid, ldsSOoidLen);

	Set *s = new Set(any);
	
	list<DO> m;
	m.push_back(*ct);
	m.push_back(*s);

	Sequence *seq = new Sequence(m);
	return seq;
}

/*
Function makes the content messageDigestAttribute which is used to make SignedAttributes
Arguments: ldsSecurityObject, length of ldsSecurityObject, algorithm to compute hash of ldsSecurityObject
Returns: Do of the form
	Sequence of
		Object Identifier of message digest attribute	
		Octet String of Hash computed of LDSSecurityObject
*/
static DO* createMsgDigestAttr(BYTE *ldsSO, int len, int hashAlgo) {
	OctetString *ot;
	BYTE *buf;
	switch (hashAlgo) {
	case SHA1: 
		buf  = new BYTE[SHA1_DIGEST_LENGTH];
		SHA1_Data(ldsSO, len, buf);		
		ot = new OctetString(SHA1_LENGTH, buf);
		break;
	case SHA256:
		buf  = new BYTE[SHA256_DIGEST_LENGTH];
		SHA256_Data(ldsSO, len, buf);
		ot = new OctetString(SHA256_LENGTH, buf);
		break;
	case SHA384:
		buf  = new BYTE[SHA384_DIGEST_LENGTH];
		SHA384_Data(ldsSO, len, buf);		
		ot = new OctetString(SHA384_LENGTH, buf);
		break;
	case SHA512:
		buf  = new BYTE[SHA512_DIGEST_LENGTH];
		SHA512_Data(ldsSO, len, buf);		
		ot = new OctetString(SHA512_LENGTH, buf);
		break;
	}
	Set *s = new Set(ot);

	list<DO> n;
	ObjectIdentifier *tc = new ObjectIdentifier(msgDigestoid, msgDigestoidLen);
	n.push_back(*tc);
	n.push_back(*s);

	Sequence *seq = new Sequence(n);
	return seq;
}

/*
Arguments: ldsSecurityObject, length of ldsSecurityObject, algorithm to compute hash of ldsSecurityObject
Returns: DO of the form of
	Context Specific
		Sequence of 
			Content Type Attribute (as before)
			Message Digest Attribute (as before)
*/
static DO* createSignedAttributes(BYTE *ldsSO, int len, int hashAlgo) {
	DO *contentTypeAttr = createContentTypeAttr();
	DO *msgDigestAttr = createMsgDigestAttr(ldsSO, len, hashAlgo);
	
	list<DO> l;
	l.push_back(*contentTypeAttr);
	l.push_back(*msgDigestAttr);

	ContextSpecific *cs = new ContextSpecific(0, l);
	return cs;
}

/*
Arguments: BYTE* certificare and length of certificate
Returns: All the appropriate DOs from the certificate, which are used as part of SignerInfo
*/
static DO* getSignerIden(BYTE *certDO, int certLen) {
	list<DO> level1 = getList(certLen, certDO);

	DO &l1seq =  level1.front();
	
	list<DO> level2 = getList(l1seq.getLength(),l1seq.getValue());
	DO &l2seq =  level2.front();

	list<DO> level3 = getList(l2seq.getLength(),l2seq.getValue());
	level3.pop_front();

	DO& tmp = level3.front();
	int seriallen = tmp.getLength();

	BYTE *serialno = new BYTE[seriallen+2];

	tmp.getTotalBytes(serialno);

	level3.pop_front();
	level3.pop_front();

	BYTE *info = new BYTE[level3.front().getLength()+2];
	level3.front().getTotalBytes(info);

	DO *in = new DO(serialno);
	DO *form = new DO(info);	
	list<DO> l;
	l.push_back(*form);
	l.push_back(*in);
	Sequence *g = new Sequence(l);
	return g;
}	
/*
Arguments: Signature, dataToSign, algorithm for hash of LDS, algorithm to compute signature, byte array of the certificate
Returns: DO of the form 
		Sequence of
			Integer: Version = 1
			Signer identifier(?) of the Certificate
			ObjectIdentifier of algorithm to compute hash of LDSSecurityObject
			SignedAttributes is DO of signedData as passed
			ObjectIdentifier of signature algorithm to compute signature
			OctetString of signature value
*/
static DO* createSignerInfo(BYTE *signature, int signLen, BYTE *signedData, int signedDataLen, int hashAlgoforLDS, int signAlgo, BYTE *certDO, int certLen) {

	BYTE ver[1] = {0x01};
	ASNInteger *version = new ASNInteger(ver, 1);


	DO *sid = getSignerIden(certDO, certLen);
	ObjectIdentifier *halgo = new ObjectIdentifier(algolist[hashAlgoforLDS].oid, algolist[hashAlgoforLDS].oidLength);	
	DO *hashAlgorithm = createHashAlgorithm(halgo);
	DO *sigAtr = new DO(signedData);
	DO *signedAttrs = new Tagged(0, 0xa0, sigAtr);
	
	ObjectIdentifier *salgo = new ObjectIdentifier(algolist[signAlgo].oid, algolist[signAlgo].oidLength);
	DO *signatureAlgorithm = createHashAlgorithm(salgo);

	OctetString *signValue = new OctetString(signLen, signature);

	list<DO> l;

	l.push_back(*version);
	l.push_back(*sid);
	l.push_back(*hashAlgorithm);
	l.push_back(*signedAttrs);
	l.push_back(*signatureAlgorithm);
	l.push_back(*signValue);

	Sequence *seq = new Sequence(l);
	Set *s = new Set(seq);	
	return s;
}

/*
Arguments: DG dgs[]: array of DGs
	   numDG: number of DGs
	   MDAlgoforDGs: algorithm to compute hash of DG Data
	   ldsSO: pointer to an array of BYTE in which the created LDSSecurityObject is stored
	   ldsSOLen: integer that provides size of ldsSO buffer
	   hashAlgoforLDS: algorithm to compute hash of LDSSecurityObject
	   dataToSign: buffer to store data bytes, which must be signed by application in order to create EF.SOD
	   dataToSignLen: integer that provides size of dataToSign buffer
Returns: LDSSecurity Object DO of the form
	Sequence
		Integer version
		HashAlgorithm hashAlgoforDGs
		Sequence of
			DataGroupHashes
The function returns an integer to indicate success of errors like MDAlgoforDGs not supported, hashALgoforLDS not supported, insufficient length of ldsSO buffer and insufficient length of dataToSign buffer.
*/
int createLDSSecurityObject(DG dgs[], int numDG, const char *MDAlgoforDGs, 
	BYTE *ldsSO, int *ldsSOLen, const char *hashAlgoforLDS, BYTE *dataToSign, 
	int *dataToSignLen) {
	int whichAlgo = -1;
	int i = 0;
	BYTE ver[1] = {0x00};
	ASNInteger *version = new ASNInteger(ver, 1);
	
	ObjectIdentifier *halgo;
	for (i=0; i < 4; i++) {
		if(strcmp(MDAlgoforDGs, algolist[i].algo) == 0) {
			halgo = new ObjectIdentifier(algolist[0].oid, algolist[0].oidLength);
			whichAlgo = i;
		}
	}
	if (whichAlgo < 0) return EFSOD_ERROR_DGHASHALGO;
	DO *hashAlgorithm = createHashAlgorithm(halgo);
	list<DO> dlist;
	for (int i=0; i < numDG; i++) {
		dlist.push_back(*createDataGroupHash(&dgs[i], whichAlgo));
	}
	Sequence *DataGroup = new Sequence(dlist);
	
	list<DO> ldsList;
	ldsList.push_back(*version);
	ldsList.push_back(*hashAlgorithm);
	ldsList.push_back(*DataGroup);

	Sequence *ldsSecurityObject;
	ldsSecurityObject = new Sequence(ldsList);

	int ldsLength = ldsSecurityObject->getTotalLength();
	
	if (ldsLength > *ldsSOLen) return EFSOD_ERROR_LDS_LEN;
	else {
		*ldsSOLen = ldsLength;
		ldsSecurityObject->getTotalBytes(ldsSO);
	}

//ldsSecurityObject formation ends
//signedData formation starts

	whichAlgo = -1;
	for (i=0; i < 4; i++) {
		if(strcmp(hashAlgoforLDS, algolist[i].algo) == 0) {
			whichAlgo = i;
		}
	}
	if (whichAlgo < 0) return EFSOD_ERROR_SODHASHALGO;
	
	DO *data = createSignedAttributes(ldsSO, ldsLength, whichAlgo);
	
	Tagged *td = new Tagged(0, 0x31, data);
	int SDlength = td->getTotalLength();
	if (SDlength > *dataToSignLen) return EFSOD_ERROR_SIGNEDDATA_LEN;
	else {
		*dataToSignLen = SDlength;
		td->getTotalBytes(dataToSign);
	}

	return EFSOD_SUCCESS;	
}

/*
Function created the entire EFSOD file
Arguments: BYTE* ldsSO: BYTE array of LDSSecurityObject as created by createLDSSecurityObject() function above
	   int ldsSOLen: length of LDSSecurityObject
	   const char* hashAlgoforLDS: allowed hash algorithms to compute hash of LDSSecurityObject which would be further explained
	   const char* cert: path to certificate file
	   BYTE* signedData: same as the dataToSign, which is computed by createLDSSecurityObject() function above
	   int signedDataLen: length of signedData
	   BYTE *signature: BYTE array of signature, given as input
	   int signLength: length of signature
	   const chat* signAlgo: allowed signature algorithms used to sign the data to compute the signature 
	   BYTE *sod: created BYTE array of EF.SOD file would be stored in this
	   int sodLen: length of sod
Returns: Entire EF.SOD file in BYTE array of sod, and its length get stored in sodLen
The function returns an integer to indicate success or errors like, hashALgoforLDS not supported, signAlgo not supported, certificate file does not exist or not readable, insufficient space in sod.
*/
int createSOD(BYTE *ldsSO, int ldsSOLen, const char *hashAlgoforLDS, const char *cert, BYTE *signedData, int signedDataLen, BYTE *signature, int signLength, const char *signAlgo, BYTE *sod, int *sodLen) {

	int whichHashAlgo = -1;
	int whichSignAlgo = -1;
	int i = 0;

	BYTE ver[1] = {0x03};
	ASNInteger *version = new ASNInteger(ver, 1);

	ObjectIdentifier *halgo;
	for (i = 0; i < 4; i++) {
		if (strcmp(hashAlgoforLDS, algolist[i].algo) == 0) {
			halgo = new ObjectIdentifier(algolist[i].oid, algolist[i].oidLength);
			whichHashAlgo = i;
		}
	}
	if (whichHashAlgo < 0) return EFSOD_ERROR_SODHASHALGO;

	for (i = 4; i < 8; i++) {
		if(strcmp(signAlgo, algolist[i].algo) == 0) {
			whichSignAlgo = i;
		}
	}
	if(whichSignAlgo < 0) return EFSOD_ERROR_SODSIGNALGO;
	
	DO *digestAlgorithms = createDigestAlgorithms();
	DO *ldsDO = new DO(ldsSO);
	DO *encapContentInfo = createEncapContentInfo(ldsDO);

	int len = -1;
	BYTE *certInfo = readCertificate(cert, &len);

	if (!(certInfo)) return EFSOD_ERROR_CERT_FILE;
	DO *certDO = new DO(certInfo); 
	list<DO> a;
	a.push_back(*certDO);

	DO *certDOCS = new ContextSpecific(0, a);
	
	DO *SignerInfos = createSignerInfo(signature, signLength, signedData, signedDataLen, whichHashAlgo, whichSignAlgo, certInfo, len);

	list<DO> l;
	l.push_back(*version);
	l.push_back(*digestAlgorithms);
	l.push_back(*encapContentInfo);
	l.push_back(*certDOCS);
	l.push_back(*SignerInfos);
	Sequence *seq = new Sequence(l);

	BYTE *temp = (BYTE*)malloc(seq->getTotalLength());
	seq->getTotalBytes(temp);	
	ContextSpecific *cs = new ContextSpecific(0, seq->getTotalLength(), temp);
	ObjectIdentifier *sdOid = new ObjectIdentifier(signedDataoid, signedDataoidLen);
	
	list<DO> n;
	n.push_back(*sdOid);
	n.push_back(*cs);
	Sequence *s = new Sequence(n);

	temp = (BYTE*)malloc(s->getTotalLength());
	s->getTotalBytes(temp);
	ApplicationSpecific *as = new ApplicationSpecific(23, s->getTotalLength(), temp);

	int sdLength = as->getTotalLength();
	if (sdLength > *sodLen) return EFSOD_ERROR_SIGNEDDATA_LEN;
	else {
		*sodLen = sdLength;
		as->getTotalBytes(sod);
	}

	return EFSOD_SUCCESS;
}

//EF.COM
int createEFCOM(BYTE* ef, int len) {
	DO* ldsver = new DO(TAG_LDS_VERSION, 4, LDS_VERSION);
	DO* uver = new DO(TAG_UNICODE_VERSION, 6, UNICODE_VERSION);
	DO* dlist = new DO(TAG_DG_LIST, 5, DGL);

	list<DO> l;
	l.push_back(*ldsver);
	l.push_back(*uver);
	l.push_back(*dlist);
	Sequence *s = new Sequence(l);
	Tagged *t = new Tagged(0, TAG_EFCOM, s);

	t->getTotalBytes(ef);
	if(t->getTotalLength() > len) return ERROR_MEM_INSUF;
	else return t->getTotalLength();
}

};